package jokode

import (
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
	"strings"
	"sync"
	"time"
)

const providerName = "JoLogger Provider powered by ZapLog"

type loggerProviderZap struct {
	locker         *sync.RWMutex
	baseLogger     *zap.Logger
	loggerInstance *zap.SugaredLogger
}

func newLoggerProviderZap() *loggerProviderZap {
	return &loggerProviderZap{
		locker: &sync.RWMutex{},
	}
}

func (lp *loggerProviderZap) GetName() string {
	return providerName
}

func (lp *loggerProviderZap) InitLogger(cfg *LogConfig) (JoLogger, error) {
	cfg = FixLogConfigDefValues(cfg)
	level := parseLogLevel(cfg.Level)

	core := zapcore.NewCore(getFileEncoder(), getFileWriter(cfg), level)

	if cfg.OutToStdIo {
		core = zapcore.NewTee(zapcore.NewCore(getConsoleEncoder(), getConsoleWriter(), level),
			core)
	}

	var baseLogger *zap.Logger
	if cfg.TraceStack {
		baseLogger = zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))
	} else {
		baseLogger = zap.New(core, zap.AddCaller())
	}

	lp.locker.Lock()
	defer lp.locker.Unlock()

	lp.baseLogger = baseLogger
	lp.loggerInstance = baseLogger.Sugar()

	return lp.loggerInstance, nil
}

func (lp *loggerProviderZap) GetLogger() JoLogger {
	return lp.loggerInstance
}

func (lp *loggerProviderZap) getLoggerInternal() *zap.SugaredLogger {
	lp.locker.RLock()
	defer lp.locker.RUnlock()
	return lp.loggerInstance
}

func parseLogLevel(level string) zapcore.Level {
	upper := strings.ToUpper(level)
	switch upper {
	case LogLevelStrDebug:
		return zap.DebugLevel
	case LogLevelStrInfo:
		return zap.InfoLevel
	case LogLevelStrWarn:
		return zap.WarnLevel
	case LogLevelStrError:
		return zap.ErrorLevel
	case LogLevelStrFatal:
		return zap.FatalLevel
	default:
		return zap.FatalLevel
	}
}

func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendString(t.Format(DefTimeFormatMs))
}

func getConsoleEncoder() zapcore.Encoder {
	encodeCfg := zap.NewDevelopmentEncoderConfig()
	encodeCfg.EncodeTime = timeEncoder
	encodeCfg.TimeKey = "time"
	encodeCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
	encodeCfg.EncodeCaller = zapcore.ShortCallerEncoder
	return zapcore.NewConsoleEncoder(encodeCfg)
}

func getFileEncoder() zapcore.Encoder {
	encodeCfg := zap.NewProductionEncoderConfig()
	encodeCfg.EncodeTime = timeEncoder
	encodeCfg.TimeKey = "time"
	encodeCfg.EncodeLevel = zapcore.CapitalLevelEncoder
	encodeCfg.EncodeCaller = zapcore.ShortCallerEncoder
	return zapcore.NewJSONEncoder(encodeCfg)
}

func getConsoleWriter() zapcore.WriteSyncer {
	return zapcore.AddSync(os.Stdout)
}

func getFileWriter(cfg *LogConfig) zapcore.WriteSyncer {

	lumberJackLogger := &lumberjack.Logger{
		Filename:   cfg.FileName,
		MaxSize:    cfg.MaxSize,
		MaxAge:     cfg.MaxAge,
		MaxBackups: cfg.MaxBackups,
		Compress:   false,
	}

	return zapcore.AddSync(lumberJackLogger)
}
